#include "log.h"
#include "args.h"
#include "copy.h"
#include "../common.h"
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#define REDIRECT_OUTPUT ">/dev/null 2>&1"
#define CREATE_FILE_CMD "dd if=/dev/zero of=%s/%s bs=%d count=1 %s"
#define CREATE_LOOP_CMD "losetup %s %s/%s %s"
#define FORMAT_CMD "mkfs.%s %s -b %d %s"
#define MOUNT_CMD "mount %s %s %s"
#define CREATE_MOUNTPOINT_CMD "mkdir -p %s %s"
#define FIND_LOOP_CMD "losetup -f"

void __append_iovec(struct iovec* iov, int& seq, const char* value)
{
	struct nlmsghdr* nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD));
	nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	nlh->nlmsg_pid = getpid();
	nlh->nlmsg_flags = 0;
	strcpy((char*)NLMSG_DATA(nlh), value);
	iov[seq].iov_base = (void*)nlh;
	iov[seq].iov_len = nlh->nlmsg_len;
	++seq;
}

ssize_t build_file_tree(int fd, void* addr)
{
	int param_num = 8;
	struct sockaddr_nl* dst_addr = (struct sockaddr_nl*)addr;
	if (camera_path_tail) ++param_num += camera_path_tail->seq + 1;
	if (exclude_path_tail) ++param_num += exclude_path_tail->seq + 1;
	if (filter_tail) ++param_num += filter_tail->seq + 1;

	int seq = 0;
	struct nlmsghdr* nlh = NULL;
	struct iovec iov[param_num];
	memset(iov, 0, sizeof(iovec)*param_num);

	if (camera_path_tail) {
		__append_iovec(iov, seq, CAMERA_LONG);
		str_node* node = camera_path_head;
		while (node) {
			__append_iovec(iov, seq, node->str);
			node = node->next;
		}		
	}	
	
	if (exclude_path_tail) {
		__append_iovec(iov, seq, EXCLUDE_LONG);
		str_node* node = exclude_path_head;
		while (node) {
			__append_iovec(iov, seq, node->str);
			node = node->next;
		}		
	}

	if (filter_tail) {
		__append_iovec(iov, seq, FILTER_LONG);
		str_node* node = filter_head;
		while (node) {
			__append_iovec(iov, seq, node->str);
			node = node->next;
		}		
	}

	__append_iovec(iov, seq, TO_LONG);
	__append_iovec(iov, seq, save_to);
	__append_iovec(iov, seq, NAME_LONG);
	__append_iovec(iov, seq, name);
	__append_iovec(iov, seq, MOUNT_LONG);
	__append_iovec(iov, seq, mount_to);
	__append_iovec(iov, seq, LOG_LONG);
	if (log_to) __append_iovec(iov, seq, log_to);
	else __append_iovec(iov, seq, "");

	struct msghdr msg;
	memset(&msg, 0 ,sizeof(struct msghdr));
	msg.msg_name = (void*)dst_addr;
	msg.msg_namelen = sizeof(struct sockaddr_nl);
	msg.msg_iov = iov;
	msg.msg_iovlen = param_num;
	return sendmsg(fd, &msg, 0);
}

ssize_t prepare_mount_point(int fd)
{
	int err;
	ssize_t seq;
	int space;
	char buf[CMD_LEN];
	int is_ext4 = 0;
	struct iovec iov;
	struct mmsghdr mmsg[1];
	struct sockaddr_nl addr;
	struct timespec timeout;

	struct nlmsghdr* nlhdr = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD));
	nlhdr->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	nlhdr->nlmsg_pid = getpid();
	nlhdr->nlmsg_flags = 0;
	iov.iov_base = (void*)nlhdr;
	iov.iov_len = nlhdr->nlmsg_len;

	mmsg[0].msg_hdr.msg_name = (void*)&addr;
	mmsg[0].msg_hdr.msg_namelen = sizeof(struct sockaddr_nl);
	mmsg[0].msg_hdr.msg_iov = &iov;
	mmsg[0].msg_hdr.msg_iovlen = 1;
	
	timeout.tv_sec = RECV_TIMEOUT;
	timeout.tv_nsec = 0;
	//recvmsg(fd, &msg, 0);
	if (recvmmsg(fd, mmsg, 1, 0, &timeout) == -1) 
	        CAMERA_EXIT("Failed to receive reply from camera.ko on building file tree");

#ifdef TEST
	if(test_code == BUILD_TREE) exit(EXIT_SUCCESS);
#endif

	const char* rawHead = (const char*)NLMSG_DATA(nlhdr);
	const char* rawTail = rawHead;
	while (*rawTail!=';') rawTail++;
	memset(buf, 0, CMD_LEN);
	memcpy(buf, rawHead, rawTail-rawHead);
	space = atoi(buf);
	if (space < 0) {
		if (space == READ_ARGS_ERR) 
		        CAMERA_EXIT("camera.ko failed to read args!");
		else if (space == BUILD_TREE_ERR)
                        CAMERA_EXIT("camera.ko failed to build file tree!");
		else if (space == ALLOC_ELEM_ERR)
                        CAMERA_EXIT("camera.ko failed to alloc inner struct!");
	}

	++rawTail;
	rawHead = rawTail;
	while (*rawTail!=';') rawTail++;
	memset(buf, 0, CMD_LEN);
	memcpy(buf, rawHead, rawTail-rawHead);
	is_ext4 = atoi(buf);

	++rawTail;
	rawHead = rawTail;
	while (*rawTail!=';') rawTail++;
	memset(buf, 0, CMD_LEN);
	memcpy(buf, rawHead, rawTail-rawHead);
	seq = atoi(buf);
	
	block_size = atoi(++rawTail);

	char create_file_cmd[CMD_LEN];	
	memset(create_file_cmd, 0, CMD_LEN);
	sprintf(create_file_cmd, CREATE_FILE_CMD, save_to, name, 
		append_space*KUNIT + atoi((const char*)NLMSG_DATA(nlhdr)), REDIRECT_OUTPUT);
	CAMERA_LOG("Run command: %s", create_file_cmd);
	system(create_file_cmd);

	char loop_dev[CMD_LEN];
	memset(loop_dev, 0, CMD_LEN);
	FILE* stream = popen(FIND_LOOP_CMD, "r");
	fread(loop_dev, sizeof(char), CMD_LEN, stream);
	fclose(stream);
	int loop_len = strlen(loop_dev);
	if (!loop_len) CAMERA_EXIT("Failed to create loop device");
	else loop_dev[loop_len-1] = '\0';

	char create_loop_cmd[CMD_LEN];	
	memset(create_loop_cmd, 0, CMD_LEN);
	sprintf(create_loop_cmd, CREATE_LOOP_CMD, loop_dev, save_to, name, REDIRECT_OUTPUT);
	CAMERA_LOG("Run command: %s", create_loop_cmd);
	system(create_loop_cmd);
	
	char format_cmd[CMD_LEN];	
	memset(format_cmd, 0, CMD_LEN);
	sprintf(format_cmd, FORMAT_CMD, is_ext4 ? "ext4" : "ext3", loop_dev, block_size, REDIRECT_OUTPUT);
	CAMERA_LOG("Run command: %s", format_cmd);
	system(format_cmd);

	char create_mount_point_cmd[CMD_LEN];	
	memset(create_mount_point_cmd, 0, CMD_LEN);
	sprintf(create_mount_point_cmd, CREATE_MOUNTPOINT_CMD, mount_to, REDIRECT_OUTPUT);
	CAMERA_LOG("Run command: %s", create_mount_point_cmd);
	system(create_mount_point_cmd);

	char mount_cmd[CMD_LEN];	
	memset(mount_cmd, 0, CMD_LEN);
	sprintf(mount_cmd, MOUNT_CMD, loop_dev, mount_to, REDIRECT_OUTPUT);
	CAMERA_LOG("Run command: %s", mount_cmd);
	system(mount_cmd);

	return seq;
}

ssize_t start_copy(int fd, void* addr, ssize_t seq)
{
	struct iovec iov;
	struct sockaddr_nl* dst_addr = (struct sockaddr_nl*)addr;
	struct nlmsghdr* nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD));
	nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	nlh->nlmsg_pid = getpid();
	nlh->nlmsg_flags = 0;

	char seqStr[CMD_LEN];
	memset(seqStr, 0, CMD_LEN);
	sprintf(seqStr, "%ld", seq);
	strcpy((char*)NLMSG_DATA(nlh), seqStr);
	iov.iov_base = (void*)nlh;
	iov.iov_len = nlh->nlmsg_len;

	struct msghdr msg;
	memset(&msg, 0 ,sizeof(struct msghdr));
	msg.msg_name = (void*)dst_addr;
	msg.msg_namelen = sizeof(struct sockaddr_nl);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	return sendmsg(fd, &msg, 0);
}

size_t __receive_progress(int fd)
{
	int err;
	ssize_t seq;
	int space;
	char buf[CMD_LEN];
	struct iovec iov;
	struct mmsghdr mmsg[1];
	struct sockaddr_nl addr;
	struct timespec timeout;

	struct nlmsghdr* nlhdr = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PAYLOAD));
	nlhdr->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	nlhdr->nlmsg_pid = getpid();
	nlhdr->nlmsg_flags = 0;
	iov.iov_base = (void*)nlhdr;
	iov.iov_len = nlhdr->nlmsg_len;

	mmsg[0].msg_hdr.msg_name = (void*)&addr;
	mmsg[0].msg_hdr.msg_namelen = sizeof(struct sockaddr_nl);
	mmsg[0].msg_hdr.msg_iov = &iov;
	mmsg[0].msg_hdr.msg_iovlen = 1;

	timeout.tv_sec = RECV_TIMEOUT;
	timeout.tv_nsec = 0;

	if (recvmmsg(fd, mmsg, 1, 0, &timeout) == -1)
                CAMERA_EXIT("Failed to query progress");
       
	return (size_t)atoi((const char*)NLMSG_DATA(nlhdr));
}

size_t query_progress(int fd, void* addr, ssize_t seq)
{
	int pos = 0;
	size_t prog = 101;
	struct iovec iov[2];
	char seqStr[CMD_LEN];
	struct nlmsghdr* nlh = NULL;
	struct sockaddr_nl* dst_addr = (struct sockaddr_nl*)addr;

	memset(seqStr, 0, CMD_LEN);
	sprintf(seqStr, "%ld", seq);
	memset(iov, 0, sizeof(iovec)*2);

	__append_iovec(iov, pos, QUERY_PROGRESS);
	__append_iovec(iov, pos, seqStr);

	struct msghdr msg;
	memset(&msg, 0 ,sizeof(struct msghdr));
	msg.msg_name = (void*)dst_addr;
	msg.msg_namelen = sizeof(struct sockaddr_nl);
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	sendmsg(fd, &msg, 0);
	return __receive_progress(fd);
}
